﻿#pragma once



#include "qstring.h"
using namespace micro::utils;



#include "qarchive.h"
#include "qfile.h"
using namespace micro::files;


#include<objidl.h> //IStream
#include<shtypes.h> //LPITEMIDLIST


namespace micro
{

namespace shell
{




class QPidl
{
public:
    LPITEMIDLIST m_p; //absolute ???

    // Constructor/Destructor

    QPidl() : m_p( nullptr )
    {
    }

    ~QPidl()
    {
        Delete();
    }


    QPidl( const QPidl & pidl )
    {
        m_p = ILCloneFull( pidl.m_p );
    }

    QPidl( LPITEMIDLIST pidl )
    {
        m_p = ILCloneFull( pidl );
    }


    QPidl( PCUITEMID_CHILD pidl )
    {
        m_p = ( PIDLIST_ABSOLUTE ) ILCloneChild( pidl );
    }

    QPidl( PCIDLIST_ABSOLUTE pidl, PCUIDLIST_RELATIVE pidlPath )
    {
        m_p = ::ILCombine( pidl, pidlPath );
    }

    QPidl( PCIDLIST_ABSOLUTE pidl, PCUIDLIST_RELATIVE pidlPath, PCUITEMID_CHILD pidlChild )
    {
        m_p = ::ILCombine( pidl, pidlPath );
        m_p = ( PIDLIST_ABSOLUTE ) ::ILAppendID( m_p, ( LPSHITEMID ) pidlChild, TRUE );
    }

    // Operations

    void Delete()
    {
        if ( m_p )
            CoTaskMemFree ( m_p );

        m_p = nullptr;
    }

    bool Create( LPSHITEMID pSrcItem )
    {
        Delete();
        m_p = ( PIDLIST_ABSOLUTE ) ::ILAppendID( nullptr, pSrcItem, TRUE );
        return m_p != nullptr;
    }

    /*
       bool CreateFromPath(LPCSTR pstrFilename)
       {
          Delete();
          m_p = ::ILCreateFromPathA(pstrFilename);
          return m_p != nullptr;
       }

       bool CreateFromPath(LPCWSTR pstrFilename)
       {
          Delete();
          m_p = ::ILCreateFromPathW(pstrFilename);
          return m_p != nullptr;
       }*/


    bool CreateFromObject( IUnknown * pUnk )
    {
        Delete();
        return ::SHGetIDListFromObject( pUnk, &m_p ) == S_OK;
    }

    bool CreateFromDisplayName( PCWSTR pszName, IBindCtx * pbc = nullptr )
    {
        Delete();
        SFGAOF FlagsOut = 0;
        return    ::SHParseDisplayName( pszName, pbc, &m_p, 0, &FlagsOut )   == S_OK;
    }

    bool CreateFromKnownFolder( REFKNOWNFOLDERID FolderId, DWORD dwFlags = 0, HANDLE hToken = nullptr )
    {
        Delete();
        return ::SHGetKnownFolderIDList( FolderId, dwFlags, hToken, &m_p ) == S_OK;
    }

    bool Copy( PCIDLIST_ABSOLUTE pidl )
    {
        Delete();
        m_p = ILCloneFull( pidl );
        return m_p != nullptr;
    }

    bool Copy( LPITEMIDLIST pidl )
    {
        Delete();
        m_p = ILCloneFull(  pidl );
        return m_p != nullptr;
    }

    bool Copy( PCUITEMID_CHILD_ARRAY rgpidl, int cidl )
    {
        Delete();

        for ( int i = 0; i < cidl; i++ )
            m_p = ( PIDLIST_ABSOLUTE ) ::ILAppendID( m_p, ( LPSHITEMID ) rgpidl[i], TRUE );

        return m_p != nullptr;
    }

    bool CopyFirst( LPITEMIDLIST pidl )
    {
        Delete();
        m_p = ILCloneFirst(  pidl );
        return m_p != nullptr;
    }

    bool Append( LPITEMIDLIST pidl )
    {
        LPITEMIDLIST p = ::ILCombine( m_p, pidl );
        Delete();

        m_p = p;
        return p != nullptr;
    }

    bool AppendChild( LPITEMIDLIST pidl )
    {
        ASSERT( pidl );
        m_p = ( PIDLIST_ABSOLUTE ) ::ILAppendID( m_p, ( LPSHITEMID ) pidl, TRUE );
        return m_p != nullptr;
    }

    bool Append( const QPidl & pidl )
    {
        return Append(  pidl.m_p );
    }

    bool AppendSHItemID( LPSHITEMID pSHItemID, BOOL bAppend = TRUE )
    {
        ASSERT( pSHItemID != nullptr );
        m_p =  ::ILAppendID( m_p, pSHItemID, bAppend );
        return m_p != nullptr;
    }

    bool IsEqual( LPITEMIDLIST pidl ) const
    {
        ASSERT( m_p != nullptr );
        return toBool( ::ILIsEqual( m_p, pidl ) );
    }

    bool IsSamePIDL( LPITEMIDLIST pidl ) const
    {
        IShellFolder * pFolder = nullptr;

        if ( FAILED( ::SHGetDesktopFolder( &pFolder ) ) )
            return false;

        HRESULT Hr = pFolder->CompareIDs( 0, ( PCUIDLIST_RELATIVE ) m_p, ( PCUIDLIST_RELATIVE ) pidl );
        pFolder->Release();
        return ( SUCCEEDED( Hr ) && HRESULT_CODE( Hr ) == 0 );
    }

    QPidl & operator =( const QPidl & pidl )
    {
        Copy( pidl.m_p );
        return *this;
    }

    QPidl & operator =( LPITEMIDLIST pidl )
    {
        Copy( pidl );
        return *this;
    }

    bool operator ==( const QPidl & pidl ) const
    {
        return IsEqual( pidl.m_p ) == TRUE;
    }

    bool operator ==( LPITEMIDLIST pidl ) const
    {
        return IsEqual( pidl ) == TRUE;
    }

    bool operator !=( const QPidl & pidl ) const
    {
        return IsEqual( pidl.m_p ) != TRUE;
    }

    bool operator !=( LPITEMIDLIST pidl ) const
    {
        return IsEqual( pidl ) != TRUE;
    }

    QPidl & operator +=( const QPidl & pidl )
    {
        Append( pidl.m_p );
        return *this;
    }

    QPidl & operator +=( LPITEMIDLIST pidl )
    {
        Append( pidl );
        return *this;
    }


    QPidl operator +( const QPidl & p2 )
    {
        QPidl pidl( *this );
        pidl.Append( p2 );
        return pidl;
    }


    LPITEMIDLIST * operator&()
    {
        return ( LPITEMIDLIST * ) &m_p;
    }

    LPCITEMIDLIST * operator&() const
    {
        return ( LPCITEMIDLIST * ) &m_p;
    }

    operator LPITEMIDLIST()
    {
        return m_p;
    }


    operator LPITEMIDLIST() const
    {
        return m_p;
    }




    bool operator!() const
    {
        return IsEmpty() == true;
    }

    LPITEMIDLIST GetData() const
    {
        return m_p;
    }

    LPITEMIDLIST GetNextItem() const
    {
        ASSERT( nullptr != m_p );
        return ::ILGetNext(  m_p );
    }

    bool IsNull() const
    {
        return nullptr == m_p ;
    }

    bool IsEmpty() const
    {
        return ILIsEmpty(  m_p );
    }

    UINT GetByteSize() const
    {
        if ( m_p == nullptr )
            return 0;

        return ::ILGetSize(  m_p );
    }

    bool RemoveLastItem()
    {
        ASSERT( m_p != nullptr );
        return toBool( ::ILRemoveLastID(  m_p ) );
    }

    bool IsParent( LPITEMIDLIST pidl, bool bImmediate = true ) const
    {
        ASSERT( m_p != nullptr );
        return toBool( ::ILIsParent( m_p, pidl, bImmediate ? TRUE : FALSE ) );
    }

    PUITEMID_CHILD GetLastItem() const
    {
        ASSERT( m_p != nullptr );
        return ::ILFindLastID(  ( PCUIDLIST_RELATIVE )  m_p );
    }

    // Returns a pointer to the pidl(s) beginning at the position.
    // Actual return-type is PUIDLIST_RELATIVE!
    PUITEMID_CHILD GetItem( int iIndex ) const
    {
        if ( iIndex < 0 )
            return nullptr;

        if ( ILIsEmpty( m_p ) )
            return nullptr;

        if ( iIndex == 0 )
            return ( PUITEMID_CHILD ) m_p;


        PUIDLIST_RELATIVE pidlItem = ::ILGetNext( ( PCUIDLIST_RELATIVE ) m_p );

        while ( !ILIsEmpty( pidlItem ) )
        {
            if ( --iIndex <= 0 )
                return ( PUITEMID_CHILD ) pidlItem;

            pidlItem = ::ILGetNext( pidlItem );
        }

        return nullptr;
    }

    HRESULT CopyTo( PIDLIST_ABSOLUTE * ppPidl ) const
    {
        ASSERT( ppPidl != nullptr );
        ASSERT( *ppPidl == nullptr );
        *ppPidl = ILCloneFull( m_p );
        return S_OK;
    }

    bool IsChild() const
    {
        return ILIsChild( ( PCUIDLIST_RELATIVE ) m_p );
    }

    bool IsDesktop() const
    {

        QPidl p;
        p.CreateFromKnownFolder( FOLDERID_Desktop );
        return IsSamePIDL( p.m_p );


    }
    bool IsComputer()const
    {

        QPidl p;
        p.CreateFromKnownFolder( FOLDERID_ComputerFolder );
        return IsSamePIDL( p.m_p );


    }
    bool IsLibrary()const
    {

        QPidl p;
        p.CreateFromKnownFolder( FOLDERID_Libraries );
        return IsSamePIDL( p.m_p );
    }


    bool IsControlPanel()const
    {

        QPidl p;
        p.CreateFromKnownFolder( FOLDERID_ControlPanelFolder );
        return IsSamePIDL( p.m_p );
    }
    bool IsNetwork()const
    {

        QPidl p;
        p.CreateFromKnownFolder( FOLDERID_NetworkFolder );
        return IsSamePIDL( p.m_p );
    }


    PUIDLIST_RELATIVE FindChild( PCIDLIST_ABSOLUTE pidl ) const
    {
        ASSERT( m_p != nullptr );
        return ::ILFindChild( m_p, pidl );
    }

    UINT GetItemCount() const
    {
        if ( IsEmpty() )
            return 0;

        UINT nCount = 0;
        PCUIDLIST_RELATIVE pidlItem = m_p;

        while ( ( pidlItem = ::ILGetNext( pidlItem ) ) != nullptr )
            nCount++;

        return nCount;
    }

    void Attach( const QPidl & pidl )
    {
        Delete();
        m_p = pidl;
    }
    void Attach( PIDLIST_ABSOLUTE pidl )
    {
        Delete();
        m_p = pidl;
    }



    LPITEMIDLIST Detach()
    {
        LPITEMIDLIST pidlItem = m_p;
        m_p = nullptr;
        return pidlItem;
    }

    bool IsAligned() const
    {
        return ILIsAligned( ( PCUIDLIST_RELATIVE ) m_p );
    }

    PITEMID_CHILD CloneItem( int iIndex )
    {
        PUIDLIST_RELATIVE pidlItem = ( PUIDLIST_RELATIVE ) m_p;

        while ( --iIndex >= 0 && pidlItem != nullptr )
            pidlItem = ::ILGetNext( pidlItem );

        if ( pidlItem != nullptr )
            return ILCloneChild( ( PUITEMID_CHILD ) pidlItem );

        return nullptr;
    }

    HRESULT LoadFromStream( IStream * pStream )
    {
        ASSERT( pStream );
        Delete();
        return ::ILLoadFromStream( pStream, ( PIDLIST_RELATIVE * ) &m_p );
    }

    HRESULT SaveToStream( IStream * pStream )
    {
        ASSERT( pStream != nullptr );
        return ::ILSaveToStream( pStream, ( PIDLIST_RELATIVE ) m_p );
    }

    bool GetName( SIGDN sigdnName, QString & name )const
    {
        ASSERT( m_p != nullptr );
        LPTSTR pname;

        HRESULT h = ::SHGetNameFromIDList( m_p, sigdnName, &pname );

        if ( h == S_OK )
        {
            name = pname;
            ::CoTaskMemFree( pname );
            return true;
        }

        return false;
    }

    bool GetPath( QString & path ) const
    {


        if ( IsNull() )
            return false;


        TCHAR spath[MAX_PATH];

        if ( SHGetPathFromIDList( m_p, spath ) )
        {
            path = spath;
            return true;
        }

        return false;
    }

    bool GetLabelPath( QString & path ) const
    {
        if ( IsNull() )
            return false;


        return ( GetName( SIGDN_DESKTOPABSOLUTEEDITING,  path )  );

    }

    int GetIconIndex() const
    {
        SHFILEINFO sfi = {0};
        UINT uFlags = SHGFI_PIDL | SHGFI_SYSICONINDEX;
        SHGetFileInfo( ( LPCTSTR )m_p, 0, &sfi, sizeof( SHFILEINFO ), uFlags );
        return sfi.iIcon;
    }



    HRESULT Read( IStream * pStream )
    {
        WORD        wLen = 0;
        DWORD       dwBytesRead;
        HRESULT     ret = E_FAIL;



        Delete();

        pStream->AddRef();

        if ( SUCCEEDED(   pStream->Read(  &wLen, 2, &dwBytesRead ) ) )
        {
            //TRACE("PIDL length is %d\n", wLen);
            if ( wLen != 0 )
            {
                m_p = ( LPITEMIDLIST ) CoTaskMemAlloc  ( wLen );

                if ( SUCCEEDED( pStream->Read( m_p , wLen, &dwBytesRead ) ) )
                {
                    //TRACE("Stream read OK\n");
                    ret = S_OK;
                }
                else
                {
                    //WARN("reading pidl failed\n");
                    Delete();
                }
            }
            else
            {
                //null pidl
                m_p = nullptr;
                ret = S_OK;
            }
        }


        pStream->Release();
        return ret;
    }

    HRESULT  Write ( IStream * pStream )
    {
        WORD        wLen = 0;
        HRESULT		ret = E_FAIL;

        //TRACE_(shell)("%p %p\n", pStream, pPidl);

        pStream->AddRef();

        wLen = ILGetSize( m_p );

        if ( SUCCEEDED( pStream->Write( &wLen, 2, nullptr ) ) )
        {
            if ( SUCCEEDED( pStream->Write( m_p, wLen, nullptr ) )  )
                ret = S_OK;
        }

        pStream->Release();

        return ret;
    }



    bool Read ( QFile & file )
    {
        WORD     wLen = 0;
        DWORD    dwBytesRead = 0;
        bool     ret = false;


        Delete();


        if ( file.Read( ( LPVOID ) &wLen, 2, dwBytesRead ) )
        {
            //TRACE("PIDL length is %d\n", wLen);
            if ( wLen != 0 )
            {
                m_p = ( LPITEMIDLIST ) CoTaskMemAlloc ( wLen );

                if (   file.Read( ( LPVOID )  m_p , wLen, dwBytesRead ) )
                {
                    //TRACE("Stream read OK\n");
                    ret = true;
                }
                else
                {
                    //WARN("reading pidl failed\n");
                    CoTaskMemFree( m_p );
                    m_p = nullptr;
                }
            }
            else
            {
                m_p = nullptr;
                ret = true;
            }
        }



        return ret;
    }

    bool  Write ( QFile & file  )
    {
        WORD    wLen = 0;
        DWORD	dwBytesWrite = 0;
        bool	ret = false;


        wLen = GetByteSize();

        if ( file.Write( &wLen, 2, dwBytesWrite ) )
        {
            if ( file.Write( ( LPVOID ) m_p, wLen, dwBytesWrite )  )
                ret = true;
        }


        return ret;
    }


    friend QArchive & operator<<( QArchive &,  const QPidl &   );
    friend QArchive & operator>>( QArchive &,  QPidl &   );

    friend QPidl  operator +( const QPidl &  , const QPidl &   );
};



typedef QObArray<QPidl>  QPidlArray;

}
}